String.prototype.format = function (args)
{
    if (arguments.length == 0) return this;

    var result = this;
    if (arguments.length == 1 && typeof (args) == "object")
        for (var key in args)
            result = result.replace(new RegExp("({" + key + "})", "g"), args[key]);
    else
        for (var i = 0; i < arguments.length; i++)
            if (arguments[i] == undefined)
                return "";
            else
                result = result.replace(new RegExp("({[" + i + "]})", "g"), arguments[i]);

    return result;
};

String.prototype.toTitleCase = function () { return this.split(/\s+|_+/g).map(part => part[0].toUpperCase() + part.substr(1).toLowerCase()).join("_"); }

String.prototype.getQueryParam = function (param) { return !this.includes(`?${param}=`) && !this.includes(`&${param}=`) ? null : this.split(/&|\?/g).filter(p => p.startsWith(param + "="))[0].split("=")[1]; }

function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

function isMobile() { return /(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent); }

function clearCookie()
{
    var keys = document.cookie.match(/[^ =;]+(?=\=)/g);
    if (keys)
        for (var i = keys.length; i--;)
            document.cookie = keys[i] + '=0;path=/;expires=' + new Date(0).toUTCString();
}

function lockScreen(mask = 0.4)
{
    ox_oy = { ox: $("body").css("overflow-x"), oy: $("body").css("overflow-y") };

    $("body").css({ "overflow-x": "hidden", "overflow-y": "hidden" })
        .append(`<div id="window-modal-mask" style="background-color:rgba(0,0,0,${mask});position:absolute;z-index:1000;left:0;top:0;width:${window.screen.width}px;height:${window.screen.height}px"></div>`);

    return ox_oy;
}

function unlockScreen(ox_oy)
{
    $("body").css({ "overflow-x": ox_oy.ox, "overflow-y": ox_oy.oy }).find("#window-modal-mask").remove();
}

/*
一般情况调用为await request(...)。若产生异常，不会再执行request后续语句
如果要深入针对里面的异常进行处理，要采用await request(...).then(...).catch(...)的方式。无论request有无异常都会执行后续语句，要中止的话只能借助标志变量来判断
intent      意图描述
url         fetch地址
idempotent  是否幂等操作
params      fetch参数
modal       是否启动遮罩层以阻止界面交互
*/
async function request(intent, url, idempotent = true, params = undefined, modal = true)
{
    if (modal) var ox_oy = lockScreen(ox_oy);

    var abortCtl = new AbortController();

    let response = null;
    (async () =>
    {
        await sleep(idempotent && (params == undefined || params.method == undefined || params.method.toLowerCase() != "post") ? 3000 : 10000);
        if (response == null)
        {
            if (idempotent)
                abortCtl.abort();
            else
                alert("请稍候……，若1分钟后页面仍未能恢复，按F5刷新并检查你刚才的操作是否生效");
        }
    })();

    try
    {
        if (params == undefined)
            response = await fetch("/web/handler/" + url, { signal: abortCtl.signal });
        else
            response = await fetch("/web/handler/" + url, { ...params, signal: abortCtl.signal });
    }
    catch
    {
        if (modal) unlockScreen(ox_oy);
        return request(intent, url, idempotent, params, modal);
    }

    if (!response.ok)
    {
        let tip = `${intent}出错 - ${response.status}：${response.statusText}`;
        alert(tip);
        if (modal) unlockScreen(ox_oy);
        return Promise.reject(tip);
    }

    let json = await response.json();
    if (json.err)
    {
        alert(json.err);
        if (modal) unlockScreen(ox_oy);
        modal = false;
        if (json.err.includes("未登录，请先登录")) location.href = "/web/login.htm";
        return Promise.reject(json.err);
    }

    if (modal) unlockScreen(ox_oy);
    return Promise.resolve(json.data);
}
